﻿//-----------------------------------------------------------------------
// <copyright company="Microsoft Corporation">
//     Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace Microsoft.DevDiv.Wpf.Samples
{
    using System;
    using System.Windows;
    using System.Windows.Media;

    /// <summary>Helper class for DrawingContext operations</summary>
    public static partial class DrawingContextExtensions
    {
        /// <summary>
        /// Draw a visual tree into a DrawingContext
        /// </summary>
        /// <param name="dc">DrawingContext to draw into</param>
        /// <param name="visual">Visual to draw</param>
        /// <param name="includeRootVisual">If false the properties from the root of the tree are ignored</param>


        /// <remarks>
        /// Visuals have properties that affect rendering (e.g. Transform)
        ///    if includeRootVisual is false the visual properties from the root of the tree are ignored
        ///
        /// Some leaf property values (freezables) are shared between the drawing context and the source visual
        /// For example any brushes used in a visual's subtree will be shared with the drawing context
        /// This means that if you change a brushes opacity in the source you are changing the brushes 
        /// opacity in the destination. Today I clone and freeze such objects. 
        /// I may provide some customizable policy to choose appropriate behavior.
        /// 
        /// FrameworkElements apply an implicit mirroring transform based on a combination of their FlowDirection and their parents FlowDirection.
        /// The RTL mirroring protocol is as follows
        ///     If an FrameworkElements locally set FlowDirection is different from its Visual or Logical parents locally set FlowDirection
        ///     The FrameworkElement applies a mirroring transform on itself (and by virtue of composition transforms its children)
        ///     If the FrameworkElement renders, its OnRender is responsible for undoing the mirroring transform. This is usually done by mirroring whatever is painted when in RTL
        /// The final effect is that 
        ///     An RTL FrameworkElement with an LTR Parent and LTR children, will paint itself normally and mirror the positions of its children without mirroring the contents of its children.
        /// If you draw to a DrawingGroup object you may have to host your drawing in a FrameworkElement to avoid non-FE mirroring. 
        /// Take a look at the DrawingVisual class as a starting point for such a host.
        /// 
        /// The code may need to be updated to take Effects (not BitmapEffects) into account (e.g rasterize them as bitmaps)
        /// The code may need to be updated to take Visual3D into account (e.g rastarize them as bitmaps)
        /// 
        /// UIElement visuals trees may render in an area different from their layout
        /// E.g A large grid with a small ellipse in its center
        /// Drawing these trees into a DrawingVisual, DrawingGroup or DrawingBrush will only retain the render bounds
        /// of the drawing effectively making the Drawing have a different size from the original UIElements RenderSize.
        /// You can work around this by 
        ///     1. Clipping oversized drawings - push a clip based on UIElement.RenderSize.
        ///     2. Padding undersized drawings - drawing a UIElement.RenderSize rectangle with a thin transparent stroke and a null fill.
        /// before calling DrawingContextExtensions.DrawVisual.
        /// I may provide some customizable policy to choose appropriate behavior.
        /// </remarks>


        /// <usage>
        /// DrawingGroup dg = new DrawingGroup();
        /// 
        /// using (DrawingContext dc = dg.Open())
        /// {
        ///     DrawingContextExtensions.DrawVisual(dc, visual, false);
        /// }
        ///
        /// dg now has all the drawing instructions emitted by visual and by visual's subtree.
        /// </usage>
        public static void DrawVisual(this DrawingContext dc, Visual visual, bool includeRootVisual)
        {
            if (dc == null)
            {
                throw new ArgumentNullException("dc");
            }

            if (visual == null)
            {
                throw new ArgumentNullException("visual");
            }

            DrawVisual(new DrawingContextProxy(dc), visual, includeRootVisual);
        }

        public static void DrawVisual(this DrawingContextProxyBase dc, Visual visual, bool includeRootVisual)
        {
            if (dc == null)
            {
                throw new ArgumentNullException("dc");
            }

            if (visual == null)
            {
                throw new ArgumentNullException("visual");
            }

            int popCount = 0;
            if (includeRootVisual)
            {
                popCount = PushVisualProperties(dc, visual);
            }
            else
            {
                FrameworkElement element = visual as FrameworkElement;
                if (element != null)
                {
                    // Undo the implicit mirroring transform introduced by FrameworkElement
                    Transform rtlMirrorTransform = GetRtlMirrorTransform(element);
                    if (rtlMirrorTransform != null)
                    {
                        dc.PushTransform(rtlMirrorTransform);
                        popCount++;
                    }
                }
            }

            Drawing drawing = VisualTreeHelper.GetDrawing(visual);
            if (drawing != null)
            {
                // Prevent things like animation and databinding on the original subtree from affecting the final drawing
                if (!drawing.IsFrozen && drawing.CanFreeze)
                {
                    // We want a frozen drawing but we dont want to change the original
                    // so we freeze a clone of the original drawing
                    // TODO: I expect the call to return a tree that purely consists of drawings, no Visuals
                    //       but what happens if drawing references a VisualBrush? 
                    //       Does GetCurrentValueAsFrozen get a snapshot of the VisualBrush?
                    //       Or will I have to convert the VisualBrush to a DrawingBrush?
                    drawing = (Drawing)drawing.GetCurrentValueAsFrozen();
                }

                if (drawing != null)
                {
                    dc.DrawDrawing(drawing);
                }
            }

            int childrenCount = VisualTreeHelper.GetChildrenCount(visual);
            for (int i = 0; i < childrenCount; i++)
            {
                Visual child = VisualTreeHelper.GetChild(visual, i) as Visual;
                DrawVisual(dc, child, true);
            }

            while (popCount > 0)
            {
                dc.Pop();
                popCount--;
            }
        }

        private static int PushVisualProperties(DrawingContextProxyBase dc, Visual visual)
        {
            int popCount = 0;

            System.Windows.Media.Effects.BitmapEffect bitmapEffect = VisualTreeHelper.GetBitmapEffect(visual);
            if (bitmapEffect != null)
            {
                System.Windows.Media.Effects.BitmapEffectInput bitmapEffectInput = VisualTreeHelper.GetBitmapEffectInput(visual);

                // TODO: Does order matter? Should this be called after PushTransform
                dc.PushEffect(bitmapEffect, (bitmapEffectInput != null) ? bitmapEffectInput : new System.Windows.Media.Effects.BitmapEffectInput());
                popCount++;
            }

            DoubleCollection xSnappingGuidelines = VisualTreeHelper.GetXSnappingGuidelines(visual);
            DoubleCollection ySnappingGuidelines = VisualTreeHelper.GetYSnappingGuidelines(visual);
            GuidelineSet guideLineSet = null;
            if (xSnappingGuidelines != null || ySnappingGuidelines != null)
            {
                guideLineSet = new GuidelineSet();
                guideLineSet.GuidelinesX = xSnappingGuidelines;
                guideLineSet.GuidelinesY = ySnappingGuidelines;

                // TODO: Does order matter? Should this be called after PushTransform
                dc.PushGuidelineSet(guideLineSet);
                popCount++;
            }

            double opacity = VisualTreeHelper.GetOpacity(visual);
            if (opacity != 1.0)
            {
                dc.PushOpacity(opacity);
                popCount++;
            }

            Brush opacityMask = VisualTreeHelper.GetOpacityMask(visual);
            if (opacityMask != null)
            {
                // TODO: Does order matter? Should this be called after PushTransform
                dc.PushOpacityMask(opacityMask);
                popCount++;
            }

            Vector offset = VisualTreeHelper.GetOffset(visual);

            Transform transform = VisualTreeHelper.GetTransform(visual);

            if (offset.X != 0.0 || offset.Y != 0.0)
            {
                Matrix m = (transform != null) ? transform.Value : Matrix.Identity;
                m.Translate(offset.X, offset.Y);
                transform = new MatrixTransform(m);
                transform.Freeze();
            }

            if (transform != null)
            {
                if (!transform.IsFrozen && transform.CanFreeze)
                {
                    transform = (Transform)transform.GetCurrentValueAsFrozen();
                }

                dc.PushTransform(transform);
                popCount++;
            }

            Geometry clipGeometry = VisualTreeHelper.GetClip(visual);
            if (clipGeometry != null)
            {
                // Order matters: PushClip must not be called before PushTransform
                dc.PushClip(clipGeometry);
                popCount++;
            }

            return popCount;
        }

        private static Transform GetRtlMirrorTransform(UIElement fe)
        {
            FlowDirection flowDirection = (FlowDirection)fe.GetValue(FrameworkElement.FlowDirectionProperty);
            FlowDirection parentFlowDirection = GetParentFlowDirection(fe);

            return (flowDirection == parentFlowDirection) ? null : new MatrixTransform(-1.0, 0.0, 0.0, 1.0, fe.RenderSize.Width, 0.0);
        }

        private static FlowDirection GetParentFlowDirection(DependencyObject fe)
        {
            FlowDirection parentFlowDirection = FlowDirection.LeftToRight;

            DependencyObject parent = VisualTreeHelper.GetParent(fe);
            if (parent != null)
            {
                parentFlowDirection = GetFlowDirectionFromVisual(parent);
            }
            else
            {
                parent = LogicalTreeHelper.GetParent(fe);
                if (parent != null)
                {
                    FrameworkElement frameworkElementParent = parent as FrameworkElement;
                    if ((frameworkElementParent != null) && (frameworkElementParent is IContentHost))
                    {
                        parent = frameworkElementParent;
                    }
                    else
                    {
                        FrameworkContentElement frameworkContentElementParent = parent as FrameworkContentElement;
                        if (frameworkContentElementParent != null)
                        {
                            parent = frameworkContentElementParent;
                        }
                    }

                    parentFlowDirection = (FlowDirection)parent.GetValue(FrameworkElement.FlowDirectionProperty);
                }
            }

            return parentFlowDirection;
        }

        private static FlowDirection GetFlowDirectionFromVisual(DependencyObject visual)
        {
            for (DependencyObject current = visual; current != null; current = VisualTreeHelper.GetParent(current))
            {
                FrameworkElement element = current as FrameworkElement;
                if (element != null)
                {
                    return element.FlowDirection;
                }

                object flowDirectionValue = current.ReadLocalValue(FrameworkElement.FlowDirectionProperty);
                if (flowDirectionValue != DependencyProperty.UnsetValue)
                {
                    return (FlowDirection)flowDirectionValue;
                }
            }

            return FlowDirection.LeftToRight;
        }
    }
}